package com.rtsffm.rtd.action.controller;

import com.rtsffm.rtd.action.registry.ActionMetaRegistry;

//~--- JDK imports ------------------------------------------------------------


import com.rtsffm.rtd.action.registry.Execution;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.WeakHashMap;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

//~--- classes ----------------------------------------------------------------

public class ActionService {
    protected final ActionMetaRegistry             actionFrameworkMetaRegistry = new ActionMetaRegistry();
    private final Set<ActionServiceHook>           triggerHooks                =
        new CopyOnWriteArraySet<ActionServiceHook>();
    private final Map<Object, Set<ActionDelegate>> actionDelegatesMap;
    private final Map<Object, Set<Object>>         fieldParentsMap;
    private final Lock                             readLock, writeLock;

    //~--- constructors -------------------------------------------------------

    public ActionService() {
        ReadWriteLock lock = new ReentrantReadWriteLock();

        actionDelegatesMap = new WeakHashMap<Object, Set<ActionDelegate>>();
        fieldParentsMap    = new WeakHashMap<Object, Set<Object>>();
        readLock           = lock.readLock();
        writeLock          = lock.writeLock();
    }

    //~--- methods ------------------------------------------------------------

    public void addActionServiceHook(ActionServiceHook hook) {
        triggerHooks.add(hook);
    }

    public void registerObject(Object actionProvider, ActionDelegate... delegates) {
        Class<?> clazz;

        if ((actionProvider == null)
                ||!actionFrameworkMetaRegistry.registerMetaData(clazz = actionProvider.getClass())) {
            return;
        }

        if (delegates == null) {
            delegates = new ActionDelegate[] {};
        }

        addDelegates(actionDelegatesMap, actionProvider, delegates);
        fireActionAdded(actionProvider, getActionIdExecutionsMap(clazz, true, true, true));

        Map<Field, Map<String, ? extends Execution>> fieldExecMap =
            actionFrameworkMetaRegistry.getActionFieldMetaMap(clazz);

        for (Entry<Field, Map<String, ? extends Execution>> entry : fieldExecMap.entrySet()) {
            Field  f = entry.getKey();
            Object fieldObject;

            try {
                fieldObject = f.get(actionProvider);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }

            if (fieldObject != null) {
                addDelegates(fieldParentsMap, fieldObject, actionProvider);
            }

            fireActionAdded(fieldObject, entry.getValue());
        }
    }

    private void fireActionAdded(Object actionProvider, Map<String, ? extends Execution> executionMap) {
        for (Entry<String, ? extends Execution> entry : executionMap.entrySet()) {
            for (ActionServiceHook hook : triggerHooks) {
                hook.newAction(entry.getKey(), actionProvider);
            }
        }
    }

    public void broadcastAction(String actionId, Object context) {}

    public void performAction(String actionId, Object actionProvider, Object context) {
        Class<?> actionProvClaz;

        if (actionProvider == null) {
            return;
        } else {
            actionProvClaz = actionProvider.getClass();
        }

        // Check if the action provider is a field and prioritize
        List<Object> fieldParents = retreiveDelegates(fieldParentsMap, actionProvider);

        for (Object parent : fieldParents) {
            Map<String, Execution> parentExecMap = getActionIdExecutionsMap(parent.getClass(), true, false, false);
            Execution              execution;

            if ((execution = parentExecMap.get(actionId)) != null) {
                execution.execute(parent, actionProvider, context);

                continue;
            }

            parentExecMap = getActionIdExecutionsMap(parent.getClass(), false, true, true);

            if ((execution = parentExecMap.get(actionId)) == null) {
                return;
            }

            for (ActionDelegate delegate : retreiveDelegates(actionDelegatesMap, parent)) {
                if (delegate.isStillValid()) {
                    execution.execute(delegate, actionProvider, context);
                }
            }
        }

        Map<String, Execution> execMap;
        Execution              execution;

        // Fields are checked; Now check the actual object actions
        execMap = getActionIdExecutionsMap(actionProvClaz, true, false, false);

        if ((execution = execMap.get(actionId)) != null) {
            execution.execute(actionProvider, actionProvider, context);

            return;
        }

        // actual object is checked; Now check the actual adopted and chain actions
        execMap = getActionIdExecutionsMap(actionProvClaz, false, true, true);

        if ((execution = execMap.get(actionId)) == null) {
            return;
        }

        for (ActionDelegate delegate : retreiveDelegates(actionDelegatesMap, actionProvider)) {
            if (delegate.isStillValid()) {
                execution.execute(delegate, actionProvider, context);
            }
        }
    }

    private void triggerAction(String actionId, Object invoker, Object causer, Object context) {
        Map<String, Execution> parentExecMap = getActionIdExecutionsMap(invoker.getClass(), true, false, false);
        Execution              execution;

        if ((execution = parentExecMap.get(actionId)) != null) {
            execution.execute(invoker, causer, context);

            return;
        }

        parentExecMap = getActionIdExecutionsMap(invoker.getClass(), false, true, true);

        if ((execution = parentExecMap.get(actionId)) == null) {
            return;
        }

        for (ActionDelegate delegate : retreiveDelegates(actionDelegatesMap, invoker)) {
            if (delegate.isStillValid()) {
                execution.execute(delegate, causer, context);
            }
        }
    }

    private <T> void addDelegates(Map<Object, Set<T>> delegatesMap, Object actionProvider, T... delegates) {
        writeLock.lock();

        try {
            Set<T> existingDelegates;

            if ((existingDelegates = delegatesMap.get(actionProvider)) == null) {
                existingDelegates = Collections.newSetFromMap(new WeakHashMap<T, Boolean>());
                delegatesMap.put(actionProvider, existingDelegates);
            }

            for (T del : delegates) {
                if ((del != null) && (del != actionProvider)) {
                    existingDelegates.add(del);
                }
            }
        } finally {
            writeLock.unlock();
        }
    }

    private <T> List<T> retreiveDelegates(Map<Object, Set<T>> map, Object mainObj) {
        readLock.lock();

        try {
            Set<T> existingDelegates;

            if ((existingDelegates = map.get(mainObj)) == null) {
                return Collections.emptyList();
            }

            List<T>     newDelegates = new ArrayList<T>(5);
            Iterator<T> iter         = existingDelegates.iterator();

            while (iter.hasNext()) {
                T obj = iter.next();

                if ((obj instanceof ActionDelegate) &&!((ActionDelegate) obj).isStillValid()) {
                    iter.remove();
                }
            }

            newDelegates.addAll(existingDelegates);

            return newDelegates;
        } finally {
            readLock.unlock();
        }
    }

    //~--- get methods --------------------------------------------------------

    private Map<String, Execution> getActionIdExecutionsMap(Class<?> clazz, boolean includeOwn, boolean includeChain,
            boolean includeImports) {
        Map<String, Execution>           executionMap = new HashMap<String, Execution>();
        Map<String, ? extends Execution> tmp;

        if (includeChain && (tmp = actionFrameworkMetaRegistry.getActionChainMetaMap(clazz)) != null) {
            executionMap.putAll(tmp);
        }

        if (includeImports && (tmp = actionFrameworkMetaRegistry.getActionImportMetaMap(clazz)) != null) {
            executionMap.putAll(tmp);
        }

        if (includeOwn && (tmp = actionFrameworkMetaRegistry.getActionMetaMap(clazz)) != null) {
            executionMap.putAll(tmp);
        }

        return executionMap;
    }
}
